#ifndef LPP_ENTERO_IMPL_HPP
#define LPP_ENTERO_IMPL_HPP
/**
 * @file 
 * @author Luis Machuca Bezzaza
 * @copyright GNU LGPL License 3.0 or later
 * */

// SPDX-License-Identifier: LGPL-3.0-or-later
// See LICENSE for copyright and warranty notices, COPYING for the full text of the license.

#include <cxxomfort/algorithm.hpp>
#include <cxxomfort/type_traits.hpp> // conditional
#include <cstdio>
#include <string>

#include <iostream>
#include <iomanip>

namespace lpp { namespace entero { namespace impl {

using cxxomfort::impl::array_ref::array_ref;
using cxxomfort::impl::array_ref::carrayref;
using cxxomfort::impl::array_ref::arrayref;

template <size_t Bits> struct unsigned_least_size ;

/* 
 * select a type that is at most half the size of uintmax_t
 * this is so that working multiplicaiton, division on that type 
 * does not cause overflow
 * */
typedef std::conditional <
    (sizeof(uint64_t)<=sizeof(uintmax_t)/2) , uint64_t, 
    typename std::conditional <
        (sizeof(uint32_t)<=sizeof(uintmax_t)/2), uint32_t, uint16_t
    >::type 
>::type uintH;
enum { HZ = sizeof(uintH) };

template <typename T>
void make_zero (array_ref<T> r) {
    std::fill(std::begin(r), std::end(r), 0);
}

template <typename T>
void make_one (array_ref<T> r) {
    std::fill(std::begin(r), std::end(r), 0);
    r[0]= 1;
}

template <typename T>
void make_copy (array_ref<T> r, array_ref<T const> const s) {
    std::copy_n(std::begin(s), r.size(), std::begin(r));
}

// 
// ArrU8 to ArrUH
// Converts an ArrU8 to an ArrUH of sufficient size to contain all elements
// 

template <unsigned S>
struct ArrU8ArrUH {
    typedef std::array<uint8_t, S> ArrU8_t;
    enum { h_size = sizeof(uintH) };
    enum { wide_size = S/h_size + (S%h_size>0) };
    typedef std::array<uintH , wide_size > ArrUH_t;
    // memcpy in the following functions = processing binary buffers as char[]

    static void 
    ufill (ArrUH_t& a, uintH v) CXXO_NOEXCEPTNOTHROW {
        std::fill(begin(a), end(a), v);
    }
    
    static 
    void cpy (ArrUH_t& dest, ArrU8_t const& src) CXXO_NOEXCEPTNOTHROW {
        using namespace std;
        fill( begin(dest), end(dest), 0 );
        memcpy(dest.data(), src.data(), S);
    }
    static 
    void cpy (ArrU8_t& dest, ArrUH_t const& src) CXXO_NOEXCEPTNOTHROW {
        using namespace std;
        fill( begin(dest), end(dest), 0 );
        memcpy(dest.data(), src.data(), S);
    }
};

// 
// ArrU8Int
// Converts a sufficiently small ArrU8 to uintmax_t 
// 

// 
// multitype compare
// 

template <typename U>
int 
lsf_compare (array_ref<U const> N1, array_ref<U const> N2) {
    assert (N1.size()==N2.size());
    // run down while values in same indices are equal
    size_t SZ;
    for (SZ= N1.size()-1; SZ > 0 && (unsigned)N1[SZ]==(unsigned)N2[SZ]; --SZ) { }
    // at this point we are at the mismatch or at the lowest digit
    return ((unsigned)N1[SZ]==(unsigned)N2[SZ]) ? 0 :
           ((unsigned)N1[SZ]<(unsigned)N2[SZ]) ? -1 : 1;
}

//
// basic operations for size S 
// (can be specialized for sufficiently small S)
// 


template <typename If=void>
struct EntOps {
    
    typedef cxxomfort::impl::array_ref::array_ref<uint8_t> utype;
    typedef cxxomfort::impl::array_ref::array_ref<uint8_t const> uctype;

    
    // an Entero can be viewed as an array_ref<uint8_t> for operations
    static CXXO_CONSTEXPR14  
    uintmax_t 
    to_u (uctype const x) CXXO_NOEXCEPTNOTHROW {
		uintmax_t r= x[0];
		for (unsigned i=1; i < x.size() && i < sizeof(uintmax_t); ++i) {
			uintmax_t xi= x[i];
			r|= xi << (i*8);
		}
		return r;
    }
    
    static bool 
    all_are (uctype const n, uint8_t c) {
        using namespace std;
        //return end(n) == find_not(begin(n), end(n), c);
        for (size_t i=0; i < n.size(); ++i) { if (n[i]!=c) return false; }
        return true;
    }

    static void 
    incr (utype x) CXXO_NOEXCEPTNOTHROW {
        unsigned sum=0, carry=1;
        for (unsigned i=0; i < x.size(); ++i) {
            sum= (unsigned)x[i] + carry;
            carry= sum > 0xffU;
            x[i]= sum;
        }        
    }

    static void 
    decr (utype x) CXXO_NOEXCEPTNOTHROW {
        unsigned sum=0, borrow=1;
        for (unsigned i=0; i < x.size(); ++i) {
            sum= (unsigned)x[i] - borrow;
            borrow= sum > 0xffU;
            x[i]= sum;
        }        
    }
    
    static void 
    complement (utype x) CXXO_NOEXCEPTNOTHROW {
        for (size_t i= 0; i < x.size(); ++i) { x[i]= ~x[i]; }
    }

    /* Bit position of the leftmost '1', which is equivalent to the integer part of the log base 2 */
    static inline  
    intmax_t 
    l_1_bit (uctype const N) {
        static const int8_t ilog2_char [256] = {
            0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // 15
            4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 31
            5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
            5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 63
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 127
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 // 255
        };

        intmax_t i= -1;
        size_t ind= N.size()-1;
        uint8_t bc;
        do {
            bc= N[ind];
            if (bc != 0) {
                i = ilog2_char[bc];
                break;
                }
            } while (ind--);
        return (i<0) ? i : static_cast<intmax_t>(CHAR_BIT)*ind+i;
    }

    static void 
    lshift_bits (utype n, size_t k) CXXO_NOEXCEPTNOTHROW {
        assert(!(k > CHAR_BIT));
        if (k >= CHAR_BIT) return;
        size_t const S= n.size();
        unsigned t= 0;
        for (size_t i=S-1; i > 0; --i) {
            t= n[i-1] >> (CHAR_BIT - k);
            n[i]<<= k;
            n[i]|= t;
        }
        n[0]<<= k;
    }

    static void 
    rshift_bits (utype n, size_t k) CXXO_NOEXCEPTNOTHROW {
        assert(!(k > CHAR_BIT));
        if (k >= CHAR_BIT) return;
        size_t const S= n.size();
        unsigned s= 0;
        for (size_t i=0; i < S-1; ++i) {
            s= n[i+1] << (CHAR_BIT - k);
            n[i]>>= k;
            n[i]|= s;
        }
        n[S-1]>>= k;
    }

    static void 
    lshift (utype n, size_t K) {
        //static_assert (1 < SZ, "not_multibyte");
        size_t const M=K/CHAR_BIT;
        size_t const Z=K%CHAR_BIT;
        size_t const S=n.size();
        if (M>0) {
            memmove ( &n[M], &n[0], S-M);
            memset ( &n[0], 0, M);
        }
        if (Z>0) lshift_bits(n, Z);
    }
    
    static void 
    rshift (utype n, size_t K) {
        size_t const M=K/CHAR_BIT;
        size_t const Z=K%CHAR_BIT;
        size_t const S=n.size();
        if (M>0) {
            memmove ( &n[0], &n[M], S-M);
            memset ( &n[S-M], 0, M);
        }
        if (Z>0) rshift_bits(n,Z);
    }

}; // EntOps

enum en_native {};

template <>
struct EntOps<en_native> {
};



} // impl

} } // lpp::entero
#endif
